<?php
/*
	PookiWiki - A wiki unlike the rest.
	Copyright (C) 2012 Jordan Craw

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

	class PookiFile extends PookiFileValidator
	{
		protected $file_path;
		protected $use_absolute_path = TRUE;
		
	
		public $path;
		// Extended, un-used read-only properties
		public $size;
		public $extension_type;
		public $date_modified;
		public $date_accessed;
		
		// Necessary properties
		public $file_type;
		public $type;
		public $readable;
		public $writable;
		public $executable;
		
		protected $verified =FALSE;
		
		//Internal
		protected $base_path;
		protected $calling_path;
		protected $root_document_path;
		protected $this_relative_path;
		protected $this_relative_path_folder;
		
		public function useAbsolutePath($decision = TRUE)
		{
			$this->use_absolute_path = $decision;
		}
		
		protected function determineFileType($file_path)
		{
			$is_file = is_file($file_path);
			$is_dir = is_dir($file_path);
			
			if($is_file)
			{
				return POOKIWIKI_CONST_FILE_TYPE_FILE;
			}
			if($is_dir)
			{
				return POOKIWIKI_CONST_FILE_TYPE_FOLDER;
			}
			else
			{
				return POOKIWIKI_CONST_FILE_TYPE_UNDETERMINED;
			}
		}
		
		protected function splitDir($file_path)
		{
			$file_delimiter = str_replace(array("/","\\"),"*",$file_path);
			return explode("*",$file_delimiter);
		}
		
		protected function flattenDir($file_path)
		{
			return implode("/",$file_path);
		}
		
		protected function getLeastExistingDir($file_path)
		{
			$path = $this->splitDir($file_path);

			if(count($path))
			{
				$path_string = "";
				$path_count = count($path);
				$type = "";
				for($i = 1; $i <= $path_count; $i++)
				{
					$proper_path = "";
					if($path_count-$i != 0)
					{
						$new_path = array_slice($path,0,$path_count-$i);
						$proper_path = $this->flattenDir($new_path);
						
						$type = $this->determineFileType($proper_path);
						if($type != POOKIWIKI_CONST_FILE_TYPE_UNDETERMINED)
						{
							return $proper_path;
						}
						
					}
					else
					{
						$new_path = array_slice($path,0,1);
						$proper_path = $this->flattenDir($new_path);
					}
					$type = $this->determineFileType($proper_path);
				}
				return $type == POOKIWIKI_CONST_FILE_TYPE_UNDETERMINED ? $proper_path : false;
			}
		}
		
		protected function generateGenericPaths()
		{
			$this->base_path = __FILE__;
			$calling_path = debug_backtrace();
			$calling_path = array_pop($calling_path);
			$this->calling_path = $calling_path['file']; // Allows for multiple files linking into eachother.
			
			if(strpos($this->calling_path,".php"))
			{
				$pos = strripos($this->calling_path, "/");
				$pos = $pos? $pos : strripos($this->calling_path,"\\");
				$correct_path = substr($this->calling_path,0,$pos);
				$this->calling_path = $correct_path;
			}
			
			unset($calling_path); // Don't want that lump of data kept for any longer than necessary.
			
			$this->root_document_path = $_SERVER['DOCUMENT_ROOT'];
			$this->this_relative_path = $this->flattenDir(array_diff($this->splitDir($this->base_path),$this->splitDir($this->root_document_path)));
			
			$proper_path = $this->splitDir($this->this_relative_path);
			$proper_path = $this->flattenDir(array_slice($proper_path,0,count($proper_path)-1));

			
			$this->this_relative_path_folder = $this->root_document_path . "/" . $proper_path;
			
		}
		

		
		protected function getAdditionalPath($main_path,$check_path)
		{
			return strpos($main_path,$check_path);
		}
		
		public function isVerified()
		{
			return $this->verified;
		}
		
		public function __construct($file_path)
		{
			$this->generateGenericPaths();
			
			if($path_array = $this->__validate($file_path))
			{
				// Wahay, path is valid. 
				$this->file_path = $path_array;
				
				// Fix so we include absolute path for themes
				$this->file_path = $this->use_absolute_path ? $path_array['full_path'] : $path_array;
				
				$file_type = $this->determineFileType($this->file_path);
				
				$this->file_type = $file_type;

				// 4. What type are we dealing with here? A path, or a file?
				if($file_type != POOKIWIKI_CONST_FILE_TYPE_UNDETERMINED)
				{	
					$path = $path_array['full_path'];
					$this->writable = is_writable($path);
					$this->readable = is_readable($path);
					$this->executable = is_executable($path);
					$this->size = filesize($path);
					$this->date_accessed = fileatime($path);
					$this->date_modified = filemtime($path);
					$this->verified = TRUE;	
					
					$this->path = $path_array;

				}
				else
				{
					if($writable_dir = $this->getLeastExistingDir($file_path))
					{
												
						$check_additional_path = $this->getAdditionalPath($writable_dir,$file_path);
						if((bool)$check_additional_path)
						{
							return false;
						}
						
						$path = $this->use_absolute_path ? $this->this_relative_path_folder . "/" . $writable_dir . "/": $writable_dir;
					$this->writable = is_writable($path);
					$this->readable = is_readable($path);
					$this->executable = is_executable($path);
					$this->verified = TRUE;	
					}
					else
					{
						// There isn't ANY writable dir in the path given.
					}
					
				}
			}	
		}
	
	}
	
	class PookiFileValidator
	{
		
		protected function validatePath($path)
		{
			
			if(strlen($path))
			{	
				
				if($num_up_dirs = preg_match("#(((..)\/?\\?){1,})#",$path,$up_dir_matches))
				{
					$matches_split = $up_dir_matches[1];
					$full_match = $up_dir_matches[0];
					$path = substr($path,strlen($full_match));
				}
				// Remove trailing slash
				if(substr($path,strlen($path)-1,strlen($path)) == "/" or substr($path,strlen($path)-1,strlen($path)) == "\\")
				{
					$path = substr($path,0,strlen($path)-1);
				}
				
				if($path == POOKI_FILE_CONST_PATH_CURRENT or $path == POOKI_FILE_CONST_PATH_ROOT)
				{
					return array($path);
				}
				else
				{
										
					$parts = array("full_path","protocol_delimiter","protocol","userpass","main_domain","current_dir");
					$pattern = "#(//)?([A-Za-z0-9]{1,}[:]?[/]{1,2})?(\w+:\w+@)?(([\w\s.-_()&]+[./]?){1,})#"; // TODO: named subgroups.
					$simple_pattern = "#[A-Za-z0-9-._]+#";

					$match = preg_match_all($pattern,$path,$matches);
					return $match ? array_combine($parts,$matches) :(preg_match_all($simple_pattern,$path,$matches) ? $matches[0] : false);	
				}
				
			}
			else if(empty($path) or $path == "")
			{
				return array(POOKI_FILE_CONST_PATH_CURRENT);
			}	
			else
			{
				return FALSE;
			}
			
		}
		
		protected function correctPathArray($path)
		{
			$reconstr = array();
				
			foreach($path as $key => $value)
			{
				if(is_array($value))
				{
					$vals = array_values($value);
					if(count($vals) ==1)
					{
						if(empty($vals[0]))
						{
						$reconstr[$key] = FALSE;
						}
						else
						{
						$reconstr[$key] = $vals[0];
						}
					}
				}
			}
			
			return $reconstr;
		}
		
		protected function cleanPath($path)
		{
			return $this->correctPathArray($path); // Only key->string_val rather than other key->array->string_val
		}
		
		
		protected function validateType($path)
		{
			$protocol = NULL;
			
			if(array_key_exists("protocol", $path))
			{
				$protocol = strtolower($path['protocol']);
				
				switch($protocol)
				{
					case "ftp":
						$protocol = POOKIWIKI_CONST_FILE_TYPE_PROTOCOL_FTP;
					break;
					
					case "http":
						$protocol = POOKIWIKI_CONST_FILE_TYPE_PROTOCOL_HTTP;
					break;
					
					case "file":
						$protocol = POOKIWIKI_CONST_FILE_TYPE_PROTOCOL_FILE;
					break;
					
					default:
						$protocol = POOKIWIKI_CONST_FILE_TYPE_PROTOCOL_UNSUPPORTED;
					break;
				}
			}
			else
			{
				$protocol = POOKIWIKI_CONST_FILE_TYPE_PROTOCOL_INTERNAL;
			}
			
			return $protocol;
		}
		
		protected function getOSType($path)
		{
			return strpos($path,":/") == 1 ? POOKIWIKI_CONST_CORE_OS_TYPE_WINDOWS : 
				(strpos($path,"//") ==0 ? POOKIWIKI_CONST_CORE_OS_TYPE_WINDOWS : POOKIWIKI_CONST_CORE_OS_TYPE_LINUX_UNIX);
		}
		
		protected function correctPath($file_path)
		{
			$os_type 		= $this->getOSType($file_path);
			$path_delim 	= $os_type == POOKIWIKI_CONST_CORE_OS_TYPE_WINDOWS ? "/" : "\\";
			$other_delim 	= $path_delim == "/" ? "\\" : "/";
			$fp 			= NULL;
			
			if(strpos($file_path,$path_delim))
			{
				
				if(strpos($file_path, $other_delim) >=0)
				{
					$fp = str_replace($other_delim,$path_delim,$file_path);
				}
				else
				{
					$fp = $file_path;
				}
				
			}
			else if(strpos($file_path,$other_delim))
			{
				$fp = str_replace($other_delim,$path_delim,$file_path);
			}
			else
			{
				$fp =  $file_path;
			}
			return array($path_delim,$fp);
		}
				
		public function __validate($file_path)
		{
			// Is it actually a valid path string?
			$valid_path = $this->validatePath($file_path);
									
			// 3. If it is, are we sure what method we're using to access the resource?
			if($valid_path)
			{
				
				$path_array = $this->cleanPath($valid_path);
				$this->type = $this->validateType($path_array);
				$type = $this->type;
				
				// Not allowing "write via POST" yet.
				if($type == POOKIWIKI_CONST_FILE_TYPE_PROTOCOL_HTTP)
				{
					$this->writable = FALSE;
					$this->executable = FALSE;
				}
				
				// 4. Now we know what method we're using, do we know whether it's a path or a file?
				// Well, this means passing it back to the file class itself -- job is done here.
				return $path_array;
				
			} 
			else
			{
				return FALSE;
			}
		}
	}
	

	class PookiFileFunctions
	{
		static function readDirectoryContents($file_path)
		{
			if(is_dir($file_path))
			{
				if($f = opendir($file_path))
				{
					$c = array();
					while(($o = readdir($f)) != false)
					{
						$c[] = $o;
					}
					if(count($c))
					{
						$p = array_slice($c,2);
						return $p;
					}
					else
					{
						return false;
					}
				}
			}
		}
		
		static function readFileContents($path,$as_string = TRUE)
		{
			return file_get_contents($path);
		}
		
	}
	
	
	
	class PookiFileReader
	{
		
		public $contents_array;
		public $contents_string;
		
		protected function readContents($file_instance)
		{
			$full_path = $file_instance->path['full_path'];
			$contents = false;
			
			
			if($file_instance->file_type == POOKIWIKI_CONST_FILE_TYPE_FILE)
			{
				$contents = PookiFileFunctions::readFileContents($full_path);
				
				if(!(is_null($contents) or $contents == ""))
				{
					$this->contents_array = explode("\n",$contents);
					$this->contents_string = $contents;
				}
			}
			if($file_instance->file_type == POOKIWIKI_CONST_FILE_TYPE_FOLDER)
			{
				$contents = PookiFileFunctions::readDirectoryContents($full_path);
			}
			$this->contents_array = $contents;
			$this->contents_string = $contents;
		}
		
		public function __construct(PookiFile $file_instance)
		{	
			$this->file_instance = $file_instance;
			if($file_instance->isVerified())
			{
				if($file_instance->readable)
				{
					$this->readContents($file_instance);
				}
			}
		}
		
		public function outputArray()
		{
			return $this->contents_array;
		}
		
		public function __toString()
		{
			if($this->contents_string)
			{
				return $this->contents_string;
			}
		}
	}
		
?>